//
// Created by A4-28 on 2020/10/9.
//
#include <glad/glad.h>
#include "../camera/camera.h"
#include "../material/Shader.h"
#include "../utils/stb_image.h"
#include "../utils/Utils.h"
#include "../material/Material.h"


float cubeVertices[] = {
        // Back face
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f, // Bottom-left
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f, // bottom-right
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f, // bottom-left
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left
        // Front face
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-right
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f, // top-right
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f, // top-right
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f, // top-left
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left
        // Left face
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-right
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-left
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-left
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-left
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-right
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-right
        // Right face
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-left
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-right
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-right
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-left
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left
        // Bottom face
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // top-right
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f, // top-left
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-left
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-left
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-right
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // top-right
        // Top face
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // bottom-right
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // bottom-right
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f  // bottom-left
};
float planeVertices[] = {
        // positions          // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
        5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
        -5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,

        5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
        5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
Utils *utilss = new Utils();
GlfwInit *init = new GlfwInit();
Camera camera1(
        glm::vec3(0, 0, 3.0f),
        glm::radians(0.0f),
        glm::radians(180.0f),
        glm::vec3(0, 1.0f, 0),
        0.0005,
        0.0005,
        45.0);

void camera1Move(GLFWwindow *w, double xPos, double yPos) {
    camera1.Movement(xPos, yPos);
}

void handleInput(GLFWwindow *w) {
    if (glfwGetKey(w, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(w, true);
    }

    if (glfwGetKey(w, GLFW_KEY_A) == GLFW_PRESS) {
        camera1.SpeedX = -1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_D) == GLFW_PRESS) {
        camera1.SpeedX = 1.0f;
    } else {
        camera1.SpeedX = 0.0f;
    }

    if (glfwGetKey(w, GLFW_KEY_W) == GLFW_PRESS) {
        camera1.SpeedZ = 1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_S) == GLFW_PRESS) {
        camera1.SpeedZ = -1.0f;
    } else {
        camera1.SpeedZ = 0.0f;
    }

    if (glfwGetKey(w, GLFW_KEY_Q) == GLFW_PRESS) {
        camera1.SpeedY = 1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_E) == GLFW_PRESS) {
        camera1.SpeedY = -1.0f;
    } else {
        camera1.SpeedY = 0.0f;
    }
}

void TestDemo() {
    //初始化 glfw 版本以及core模式
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //创建窗口
    GLFWwindow *window = glfwCreateWindow(400, 400, "depthTest", NULL, NULL);
    if (window == NULL) {
        printf("create window error!!");
        glfwTerminate();
        return;
    }
    //context
    glfwMakeContextCurrent(window);
    //cursor 移动位置的回调
    glfwSetCursorPosCallback(window, camera1Move);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    //初始glad
    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        printf("init glad error");
        glfwTerminate();
        return;
    }
    //视口设置
    glViewport(0, 0, 400, 400);
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    //面剔除----> 绘制面向用户的一面，丢弃背面，
    //通过环绕顺序判断所绘制的三角形是背向三角形还是正向三角形。默认情况下，逆时针顶点所定义的三角形将会被处理为正向三角形。
    //实际的环绕顺序是在光栅化阶段进行的，也就是顶点着色器运行之后。
//    glEnable(GL_CULL_FACE);//开启面剔除
    /**
     * GL_BACK：只剔除背向面。
     * GL_FRONT：只剔除正向面。
     * GL_FRONT_AND_BACK：剔除正向面和背向面
     */
//    glCullFace(GL_BACK);
    /**
     * 怎样定义正面  GL_CCW 逆时针的面为正面 GL_CW 顺时针为正面
     */
//    glFrontFace(GL_CW);

    Shader shader = Shader();
    unsigned int ID = shader.loadShader(utilss->GetResourceDir().append("\\resource\\faceCulling\\vertex.glsl"),
                                        utilss->GetResourceDir().append("\\resource\\faceCulling\\frag.glsl"));
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    glBindVertexArray(0);
    // plane VAO
    unsigned int planeVAO, planeVBO;
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);
    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    glBindVertexArray(0);

    unsigned int a = utilss->loadTexture(utilss->GetResourceDir().append("\\resource\\marble.jpg").c_str());
    unsigned int b = utilss->loadTexture(utilss->GetResourceDir().append("\\resource\\metal.png").c_str());

    glUniform1i(glGetUniformLocation(ID, "ourTexcoord"), GL_TEXTURE0);

    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 project;
    //渲染
    while (!glfwWindowShouldClose(window)) {
        handleInput(window);

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(ID);

        model = glm::mat4(1.0f);
        view = camera1.GetViewMatrix();
        project = glm::perspective(glm::radians(camera1.FOV), (float) 800 / (float) 800, 0.1f, 100.0f);

        glUniformMatrix4fv(glGetUniformLocation(ID, "view"), 1, false, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(ID, "project"), 1, false, glm::value_ptr(project));

        // floor
        glBindVertexArray(planeVAO);
        model = glm::mat4(1.0f);
        glBindTexture(GL_TEXTURE_2D, b);
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);

        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);
        glFrontFace(GL_CCW);
        // cubes
        glBindVertexArray(cubeVAO);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glBindTexture(GL_TEXTURE_2D, a);
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);

        glDisable(GL_CULL_FACE);

        glfwPollEvents();
        glfwSwapBuffers(window);
        camera1.UpdateCameraPosition();
    }
    glfwTerminate();
}

int main() {
    TestDemo();
}